知识点名称:Handler的使用
编号: K10-2
前驱知识点编号:K10-1,K3-1
作者:
讲义内容:
1 .Handler的基本概念
在android里,Handler负责发送和处理消息,通过它还可以实现其他线程与Main线程之间的消息通讯。另外,handler还可以可以分发Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程), 也就是说Handler对象初始化后,就默认与对它初始化的进程的消息队列绑定,因此可以利用Handler所包含的消息队列,制定一些操作的顺序。
我们先来看Handler分发Message和Runnable的常用方法:
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable,long)
以上post类方法允许排列一个Runnbale对象到主线程对列中。
sendMessage(Message)
sendMessageAtTime (Message)
sendMessageDelayed (Message)
sendEmptyMessage(Message)
以上sendMessage类方法允许排列一个带数据的Message对象到MessageQueue中,等待处理。
下面通过具体的实例演示Handler的基本用法以及它是如何实现多线程之间的通讯的。
- Handler的基本用法
对于耗时操作我们会放到其他的线程中处理,多线程可以通过implements Runnable 或 extends Thread的方式实现,在此我们选择前者。多线程的核心机制就是Handler,Handler会绑定一个线程(该线程是创建Handler实例的线程,通常是主线程),进而与此线程的Looper对象和MessageQueue对象绑定,Looper负责管理线程的消息队列和消息循环,也就是它负责不停的循环的遍历队列MessageQueue,从中取出符合条件的Message,然后交给与MessageQueue绑定的Handler处理。MessageQueue是存放Message的队列,通过Handler的sendMessage方法或者Message的sendToTarget发送到MessageQueue中,而Message是线程间通讯的消息载体。
下面我们通过具体实例加以说明。本实例实现的是当开始按钮按下时,会启动一个线程,并绑定到handler中,该线程发送带有参数的message到handler的消息队列中,消息队列的另一端获取该消息,并且用该消息的参数来更新进度条,这就要用到handler的handleMessage来处理消息,处理方法是获得消息队列中的消息参数,用这些参数来完成对进度条的更新,因此需要在程序中重写handleMessage方法。
界面布局的核心代码如下:
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="显示进度" />
界面交互的核心代码如下:
public class MainActivity extends ActionBarActivity {
private ProgressBar bar;
private Button bt1;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar=(ProgressBar) this.findViewById(R.id.progressBar1);
bt1=(Button) this.findViewById(R.id.button1);
bt1.setOnClickListener(new ButtonListener());
}
class ButtonListener implements OnClickListener
{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//进度条设置成可见状态
bar.setVisibility(View.VISIBLE);
//把线程加入到线程队列中
handler.post(runnable);
}
}
//匿名内部类,用来复写Handler中的handleMessage方法
Handler handler =new Handler(){
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
bar.setProgress(msg.arg1);
handler.post(runnable);
}
};
//将要执行的操作写在线程对象的run方法中
Runnable runnable=new Runnable()
{
int i=0;
public void run() {
// TODO Auto-generated method stub
System.out.println("begin thread");
i=i+20;
//得到一个消息队列,Message由android系统提供
//handler会用到两个队列,一个是线程队列,一个是消息队列
Message msg=handler.obtainMessage();
//将msg的arg1设置成i,还有一个arg2这个参数,用这两个参数传递消息
//有点是系统系统消耗较少
msg.arg1=i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//将消息加入到消息队列中
handler.sendMessage(msg);
if(i==100)
{
//将线程对象从handler中移除
handler.removeCallbacks(runnable);
}
}
};
}
本例中,在main线程中创建了Handler对象,并重写了其handleMessage方法,用于对消息的处理。由于是在main线程中创建的Handler对象,因此Handler对象会绑定当前的main线程,并与当前activiy的Looper对象和MessageQueue对象关联。当点击按钮时,首先让进度条可见,并通过handler.post(runnable)语句将线程加入到线程队列中,进而直接调用runnable里面的run方法,这里要注意的是并没有开启新的线程,而是在当前的main线程中直接执行了run方法。在run方法中,使用handler.obtainMessage()语句,让Message对象与当前的Handler对象绑定,这样Message对象就由Handler对象发送和接收处理,由Looper负责从MessageQueue中取出Message交给Handler的handleMessage方法处理。
- Handler和多线程
Handler的post方法只负责将线程加入到线程队列中,并直接调用了线程的run方法,并没有开启新的线程,我们首先通过实例来验证,该实例的界面上包含一个按钮:
public class MainActivity extends ActionBarActivity {
Handler handler=new Handler();
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
handler.post(r);
setContentView(R.layout.activity_main);
System.out.println("activity--"+Thread.currentThread().getId());
System.out.println("activityname---"+Thread.currentThread().getName());
}
//将要执行的操作写在线程对象的run方法中
Runnable r=new Runnable()
{
public void run() {
// TODO Auto-generated method stub
System.out.println("handler--"+Thread.currentThread().getId());
System.out.println("handlername---"+Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}
运行此程序会发现需要等待10秒才会显示该按钮,由于首先执行 handler.post(r),就会先执行r的run()方法,在run()方法设置了延迟10秒。另外从下面的输出截图也可以看出main线程和r线程中打印出的线程的ID和线程的名字都是一样的,运行结果如下。
上例说明整个run中的操作和主线程处于同一个线程,那么如果run中的操作是比较耗时的,就会导致“假死”现象。那么,如何解决这种问题,也就是让handler对象绑定到处于另外一个线程的消息队列中,它们将在另外的消息队列中被处理,而不影响主线程,即通过异步解决耗时操作,下节我们引入HandlerThread来开启新的线程。
4 .HandlerThread的使用
HandlerThread可以将获取到的Looper传递给Handler对象,即由处于另外线程的Looper代替handler初始化时默认绑定的消息队列来处理消息。HandlerThread可以处理消息循环的线程,它是一个拥有Looper的线程,可以处理消息循环; 其实与其说Handler和一个线程绑定,倒不如说Handler和Looper是对应的。我们将10.2.3节中的例题加以改进,其代码如下:
public class MainActivity extends ActionBarActivity {
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//打印当前线程的ID和名字
System.out.println("activity--"+Thread.currentThread().getId());
System.out.println("activityname---"+Thread.currentThread().getName());
//生成一个HandlerThread对象
HandlerThread thread=new HandlerThread("handlerthread");
//在使用HandlerThread的getLooper()方法之前,必须先调用该类的start(),同时开启一个新线程;
thread.start();
//创建一个Handler对象,并使用HandlerThread的Looper
MyHandler handler=new MyHandler(thread.getLooper());
handler.post(r);
}
class MyHandler extends Handler{
public MyHandler(Looper looper) {
super(looper);
}
};
//将要执行的操作写在线程对象的run方法中
Runnable r=new Runnable()
{
public void run() {
// TODO Auto-generated method stub
System.out.println("handler--"+Thread.currentThread().getId());
System.out.println("handlername---"+Thread.currentThread().getName());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}
运行结果截图如下:
从图中可以看出打印的线程的ID和名字不再一样了,说明通过HandlerThread可以开启一个新的线程,实现耗时操作的异步执行。该例中至少含有两个线程,即主线程和新开启的线程。下面我们再对该例加以改进,来实现线程间的通讯,通讯内容是一个人的姓名和年龄,代码如下:
public class MainActivity extends ActionBarActivity {
private Message msg;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//打印当前线程的ID和名字
System.out.println("activity--"+Thread.currentThread().getId());
System.out.println("activityname---"+Thread.currentThread().getName());
//生成一个HandlerThread对象
HandlerThread thread=new HandlerThread("handlerthread");
//在使用HandlerThread的getLooper()方法之前,必须先调用该类的start(),同时开启一个新线程;
thread.start();
//创建一个Handler对象,并使用HandlerThread的Looper
MyHandler handler=new MyHandler(thread.getLooper());
handler.post(r);
msg=handler.obtainMessage();
//创建Bundle对象,并将要传递的数据进行封装
Bundle bundle=new Bundle();
bundle.putString("name", "zhangsan");
bundle.putInt("age", 18);
msg.setData(bundle);
//将msg发送到handler
msg.sendToTarget();
}
class MyHandler extends Handler{
public MyHandler(Looper looper) {
super(looper);
}
};
//将要执行的操作写在线程对象的run方法中
Runnable r=new Runnable()
{
public void run() {
// TODO Auto-generated method stub
System.out.println("handler--"+Thread.currentThread().getId());
System.out.println("handlername---"+Thread.currentThread().getName());
Bundle bundle=msg.getData();
String name=bundle.getString("name");
int age=bundle.getInt("age");
System.out.println("name="+name+" age="+age);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}
运行结果截图如下: